package com.baidu.iit.pxp.entity;

import com.baidu.iit.pvm.ActivityException;
import com.baidu.iit.pvm.delegate.VariableScope;
import com.baidu.iit.pxp.el.ELContext;

import java.io.Serializable;
import java.util.*;

/**
 * User: huangweili
 * Date: 14-4-28
 * Time: 下午4:02
 */
public abstract class VariableScopeImpl implements Serializable, VariableScope {


    protected Map<String, VariableInstanceEntity> variableInstances = null;
    protected List<VariableInstanceEntity> variableInstanceList = null;

    protected ELContext cachedElContext;

    protected String id = null;

    protected abstract List<VariableInstanceEntity> loadVariableInstances();

    protected abstract VariableScopeImpl getParentVariableScope();

    protected abstract void initializeVariableInstanceBackPointer(VariableInstanceEntity variableInstance);

    protected void ensureVariableInstancesInitialized() {
        if (variableInstances == null) {
            variableInstances = new HashMap<String, VariableInstanceEntity>();
            variableInstanceList = new ArrayList<VariableInstanceEntity>();
            List<VariableInstanceEntity> variableInstancesList = loadVariableInstances();
            for (VariableInstanceEntity variableInstance : variableInstancesList) {
                variableInstances.put(variableInstance.getName(), variableInstance);
                variableInstanceList.add(variableInstance);
            }
        }
    }

    public Map<String, Object> getVariables() {
        return collectVariables(new HashMap<String, Object>());
    }

    protected Map<String, Object> collectVariables(HashMap<String, Object> variables) {
        ensureVariableInstancesInitialized();
        VariableScopeImpl parentScope = getParentVariableScope();
        if (parentScope != null) {
            variables.putAll(parentScope.collectVariables(variables));
        }
        for (VariableInstanceEntity variableInstance : variableInstances.values()) {
            variables.put(variableInstance.getName(), variableInstance.getValue());
        }
        return variables;
    }

    public Object getVariable(String variableName) {
        ensureVariableInstancesInitialized();
        VariableInstanceEntity variableInstance = variableInstances.get(variableName);
        if (variableInstance != null) {
            return variableInstance.getValue();
        }
        VariableScope parentScope = getParentVariableScope();
        if (parentScope != null) {
            return parentScope.getVariable(variableName);
        }
        return null;
    }

    public Object getVariableLocal(String variableName) {
        ensureVariableInstancesInitialized();
        VariableInstanceEntity variableInstance = variableInstances.get(variableName);
        if (variableInstance != null) {
            return variableInstance.getValue();
        }
        return null;
    }

    public boolean hasVariables() {
        ensureVariableInstancesInitialized();
        if (!variableInstances.isEmpty()) {
            return true;
        }
        VariableScope parentScope = getParentVariableScope();
        if (parentScope != null) {
            return parentScope.hasVariables();
        }
        return false;
    }

    public boolean hasVariablesLocal() {
        ensureVariableInstancesInitialized();
        return !variableInstances.isEmpty();
    }

    public boolean hasVariable(String variableName) {
        if (hasVariableLocal(variableName)) {
            return true;
        }
        VariableScope parentScope = getParentVariableScope();
        if (parentScope != null) {
            return parentScope.hasVariable(variableName);
        }
        return false;
    }

    public boolean hasVariableLocal(String variableName) {
        ensureVariableInstancesInitialized();
        return variableInstances.containsKey(variableName);
    }

    protected Set<String> collectVariableNames(Set<String> variableNames) {
        ensureVariableInstancesInitialized();
        VariableScopeImpl parentScope = getParentVariableScope();
        if (parentScope != null) {
            variableNames.addAll(parentScope.collectVariableNames(variableNames));
        }
        for (VariableInstanceEntity variableInstance : variableInstances.values()) {
            variableNames.add(variableInstance.getName());
        }
        return variableNames;
    }

    public Set<String> getVariableNames() {
        return collectVariableNames(new HashSet<String>());
    }

    public Map<String, Object> getVariablesLocal() {
        Map<String, Object> variables = new HashMap<String, Object>();
        ensureVariableInstancesInitialized();
        for (VariableInstanceEntity variableInstance : variableInstances.values()) {
            variables.put(variableInstance.getName(), variableInstance.getValue());
        }
        return variables;
    }

    public Set<String> getVariableNamesLocal() {
        ensureVariableInstancesInitialized();
        return variableInstances.keySet();
    }

    public void createVariablesLocal(Map<String, ? extends Object> variables) {
        if (variables != null) {
            for (Map.Entry<String, ? extends Object> entry : variables.entrySet()) {
                createVariableLocal(entry.getKey(), entry.getValue());
            }
        }
    }

    public void setVariables(Map<String, ? extends Object> variables) {
        if (variables != null) {
            for (String variableName : variables.keySet()) {
                setVariable(variableName, variables.get(variableName));
            }
        }
    }

    public void setVariablesLocal(Map<String, ? extends Object> variables) {
        if (variables != null) {
            for (String variableName : variables.keySet()) {
                setVariableLocal(variableName, variables.get(variableName));
            }
        }
    }

    public void removeVariables() {
        ensureVariableInstancesInitialized();
        Set<String> variableNames = new HashSet<String>(variableInstances.keySet());
        for (String variableName : variableNames) {
            removeVariable(variableName);
        }
    }

    public void removeVariablesLocal() {
        List<String> variableNames = new ArrayList<String>(getVariableNamesLocal());
        for (String variableName : variableNames) {
            removeVariableLocal(variableName);
        }
    }

    public void deleteVariablesInstanceForLeavingScope() {
        ensureVariableInstancesInitialized();
        variableInstanceList.clear();
    }

    public void removeVariables(Collection<String> variableNames) {
        if (variableNames != null) {
            for (String variableName : variableNames) {
                removeVariable(variableName);
            }
        }
    }

    public void removeVariablesLocal(Collection<String> variableNames) {
        if (variableNames != null) {
            for (String variableName : variableNames) {
                removeVariableLocal(variableName);
            }
        }
    }

    public void setVariable(String variableName, Object value) {
        setVariable(variableName, value, getSourceActivityExecution());
    }

    protected void setVariable(String variableName, Object value, ExecutionEntity sourceActivityExecution) {
        if (hasVariableLocal(variableName)) {
            setVariableLocal(variableName, value, sourceActivityExecution);
            return;
        }
        VariableScopeImpl parentVariableScope = getParentVariableScope();
        if (parentVariableScope != null) {
            if (sourceActivityExecution == null) {
                parentVariableScope.setVariable(variableName, value);
            } else {
                parentVariableScope.setVariable(variableName, value, sourceActivityExecution);
            }
            return;
        }
        createVariableLocal(variableName, value);
    }

    public Object setVariableLocal(String variableName, Object value) {
        return setVariableLocal(variableName, value, getSourceActivityExecution());
    }

    public Object setVariableLocal(String variableName, Object value, ExecutionEntity sourceActivityExecution) {
        ensureVariableInstancesInitialized();
        VariableInstanceEntity variableInstance = variableInstances.get(variableName);
        if (variableInstance == null) {
            createVariableLocal(variableName, value);
        } else {
            updateVariableInstance(variableInstance, value, sourceActivityExecution);
        }

        return null;
    }

    public void createVariableLocal(String variableName, Object value) {
        createVariableLocal(variableName, value, getSourceActivityExecution());
    }


    protected void createVariableLocal(String variableName, Object value, ExecutionEntity sourceActivityExecution) {
        ensureVariableInstancesInitialized();

        if (variableInstances.containsKey(variableName)) {
            throw new ActivityException("variable '" + variableName + "' already exists. Use setVariableLocal if you want to overwrite the value");
        }

        createVariableInstance(variableName, value, sourceActivityExecution);
    }

    public void removeVariable(String variableName) {
        removeVariable(variableName, getSourceActivityExecution());
    }

    protected void removeVariable(String variableName, ExecutionEntity sourceActivityExecution) {
        ensureVariableInstancesInitialized();
        if (variableInstances.containsKey(variableName)) {
            removeVariableLocal(variableName);
            return;
        }
        VariableScopeImpl parentVariableScope = getParentVariableScope();
        if (parentVariableScope != null) {
            if (sourceActivityExecution == null) {
                parentVariableScope.removeVariable(variableName);
            } else {
                parentVariableScope.removeVariable(variableName, sourceActivityExecution);
            }
        }
    }

    public void removeVariableLocal(String variableName) {
        removeVariableLocal(variableName, getSourceActivityExecution());
    }

    protected ExecutionEntity getSourceActivityExecution() {
        return null;
    }

    protected void removeVariableLocal(String variableName, ExecutionEntity sourceActivityExecution) {
        ensureVariableInstancesInitialized();
        VariableInstanceEntity variableInstance = variableInstances.remove(variableName);
        if (variableInstance != null) {
            deleteVariableInstanceForExplicitUserCall(variableInstance, sourceActivityExecution);
            variableInstanceList.remove(variableInstance);
        }
    }

    protected void deleteVariableInstanceForExplicitUserCall(VariableInstanceEntity variableInstance, ExecutionEntity sourceActivityExecution) {
    }

    protected void updateVariableInstance(VariableInstanceEntity variableInstance, Object value, ExecutionEntity sourceActivityExecution) {

    }

    protected VariableInstanceEntity createVariableInstance(String variableName, Object value, ExecutionEntity sourceActivityExecution) {

        return null;
    }


    protected boolean isActivityIdUsedForDetails() {
        return true;
    }


    public ELContext getCachedElContext() {
        return cachedElContext;
    }

    public void setCachedElContext(ELContext cachedElContext) {
        this.cachedElContext = cachedElContext;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}
